import * as http from "http";
import * as https from "https";
import { URL } from "url";
import { Buffer } from "buffer";
import * as zlib from "zlib";

class Response {
    public httpVersion: string;
    public statusCode: number;
    public statusText: string;
    public headers: { [key: string]: string | string[] };
    public rawBody: Buffer;
    private _body?: Buffer;
    private _text?: string;
    private _json?: Object;

    constructor(
        httpVersion: string,
        statusCode: number,
        statusText: string,
        headers?: { [key: string]: string | string[] },
        rawBody?: Buffer
    ) {
        this.httpVersion = httpVersion;
        this.statusCode = statusCode;
        this.statusText = statusText;

        if (headers === undefined) {
            this.headers = {};
        } else if (headers instanceof Object) {
            this.headers = headers;
        } else {
            throw Error("不能识别的headders参数");
        }

        if (rawBody) {
            this.rawBody = rawBody;
        } else {
            this.rawBody = Buffer.alloc(0);
        }
    }

    get body(): Buffer {
        if (this._body === undefined) {
            switch (this.headers["content-encoding"]) {
                case "gzip":
                    this._body = zlib.gunzipSync(this.rawBody);
                    break;
                case "deflate":
                    this._body = zlib.inflateSync(this.rawBody);
                    break;
                case "br":
                    this._body = zlib.brotliDecompressSync(this.rawBody);
                    break;
                case "compress":
                    throw new Error("不支持 compress 压缩格式");
                case undefined:
                    this._body = this.rawBody;
                    break;
                default:
                    throw new Error(
                        `不支持的内容编码 ${this.headers["content-encoding"]}`
                    );
            }
        }

        return this._body;
    }

    get text(): string {
        if (this._text == undefined) {
            this._text = this.body.toString();
        }

        return this._text;
    }

    get json(): Object {
        if (this._json == undefined) {
            let r = JSON.parse(this.text);
            if (r instanceof Object) {
                this._json = r as Object;
            } else {
                throw new Error("body不能转成json对象");
            }
        }

        return this._json;
    }
}

async function get(
    url: string | URL,
    params?: {},
    headers?: { [key: string]: string }
): Promise<Response> {
    return new Promise((resolve, reject) => {
        if (typeof url == "string") {
            url = new URL(url);
        }
        if (params) {
            url.search = Object.entries(params).reduce((p, c) => {
                return p + `&${c[0]}=${c[1]}`;
            }, "?");
        }

        let clientRequest;
        switch (url.protocol) {
            case "https:":
                clientRequest = https.get(url, { headers: headers });
                break;
            case "http:":
                clientRequest = http.get(url, { headers: headers });
                break;
            case "unix:":
                let socketPath = (
                    url.href.match(/\/\/(\S+\.sock)/) as [string, string]
                )[1] as string;
                let path = (
                    url.href.match(/\.sock(\S+)/) as [string, string]
                )[1];
                clientRequest = http.get({
                    headers: headers,
                    socketPath: socketPath,
                    path: path,
                });
                break;
            default:
                throw new Error("不能识别的协议:" + url.protocol);
        }
        // clientRequest.setTimeout(10000);

        const chunks: Buffer[] = [];
        clientRequest.on("response", (res) => {
            res.on("data", (chunk) => {
                chunks.push(chunk);
            });
            res.on("end", () => {
                let buf = Buffer.concat(chunks);
                let r = new Response(
                    res.httpVersion,
                    res.statusCode as number,
                    res.statusMessage as string,
                    res.headers as { [key: string]: string },
                    buf
                );
                resolve(r);
            });
            res.on("error", (err) => {
                reject(err);
            });
        });

        clientRequest.on("error", (err) => {
            reject(err);
        });
    });
}

async function post(
    url: string | URL,
    params?: {},
    headers?: { [key: string]: string },
    body?: Buffer | string
): Promise<Response> {
    return new Promise((resolve, reject) => {
        if (typeof url == "string") {
            url = new URL(url);
        }
        if (params) {
            url.search = Object.entries(params).reduce((p, c) => {
                return p + `&${c[0]}=${c[1]}`;
            }, "?");
        }

        let clientRequest;
        switch (url.protocol) {
            case "https:":
                clientRequest = https.request(url, {
                    headers: headers,
                    method: "POST",
                });
                break;
            case "http:":
                clientRequest = http.request(url, {
                    headers: headers,
                    method: "POST",
                });
                break;
            case "unix:":
                let socketPath = (
                    url.href.match(/\/\/(\S+\.sock)/) as [string, string]
                )[1] as string;
                let path = (
                    url.href.match(/\.sock(\S+)/) as [string, string]
                )[1];
                clientRequest = http.request({
                    headers: headers,
                    socketPath: socketPath,
                    path: path,
                    method: "POST",
                });
                break;
            default:
                throw new Error("不能识别的协议:" + url.protocol);
        }

        clientRequest.on("error", (err) => {
            reject(err);
        });
        if (body) {
            clientRequest.write(body);
        }
        clientRequest.end();

        const chunks: Buffer[] = [];
        clientRequest.on("response", (res) => {
            res.on("data", (chunk) => {
                chunks.push(chunk);
            });

            res.on("end", () => {
                let buf = Buffer.concat(chunks);
                let r = new Response(
                    res.httpVersion,
                    res.statusCode as number,
                    res.statusMessage as string,
                    res.headers as { [key: string]: string },
                    buf
                );
                resolve(r);
            });

            res.on("error", (err) => {
                reject(err);
            });
        });
    });
}

export { get, post, Response };
